﻿using System;
using System.Collections.Generic;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;

namespace ScriptXCompiler
{
    public partial class Translator
    {
        /// <summary>
        /// when generating assign to property code, need set this flag.
        /// </summary>
        bool _isSetter = false;
        bool _rightMost = false;

        public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
        {
            if (IsPartOfDynamic(node.Expression))
            {
                _output.Write(node.Expression.GetText().ToString().Trim());//Note: Dynamic, output without change
                this.AppendCompileIssue(node, IssueType.Warning, IssueId.DynamicType);
            }
            else
            {
                var info = _semanticModel.GetSymbolInfo(node.Expression);
                if (info.Symbol != null && info.Symbol.Name == "Del" && info.Symbol.ContainingType.ToString() == "System.Delete")
                {
                    _output.Write("delete ");
                    this.MakeArgumentsList(node.ArgumentList.Arguments);
                    return node;
                }
                else if (info.CandidateSymbols.Count == 1)
                {
                    var symbol = info.CandidateSymbols[0];
                    if (symbol.Name == "Del" && symbol.ContainingType.ToString() == "System.Delete")
                    {
                        _output.Write("delete ");
                        this.MakeArgumentsList(node.ArgumentList.Arguments);
                        return node;
                    }
                }
                else
                    this.VisitExpression(node.Expression);
            }
            _output.Write("(");
            this.MakeArgumentsList(node.ArgumentList.Arguments);
            _output.Write(")");

            return node;
        }

        public override SyntaxNode VisitInitializerExpression(InitializerExpressionSyntax node)
        {
            _output.Write("[");
            int count = 0;
            foreach (var expr in node.Expressions)
            {
                Visit(expr);
                count++;
                if (count < node.Expressions.Count)
                    _output.Write(", ");
            }
            _output.Write("]");
            return node;
        }

        public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node)
        {
            switch (node.Kind)
            {
                case SyntaxKind.FalseLiteralExpression:
                    _output.Write("false");
                    break;

                case SyntaxKind.TrueLiteralExpression:
                    _output.Write("true");
                    break;

                case SyntaxKind.NullLiteralExpression:
                    _output.Write("null");
                    break;

                case SyntaxKind.NumericLiteralExpression:
                    string num = node.Token.ValueText;
                    if (!char.IsNumber(num[num.Length - 1]))
                    {
                        num = num.Substring(0, num.Length - 1);
                    }
                    _output.Write(num);
                    break;

                case SyntaxKind.StringLiteralExpression:
                    string str = node.Token.ValueText.Bracketing(StringPair.DoubleQuote);
                    if (str[0] == '@')
                    {
                        str = str.Substring(1);

                        this.AppendCompileIssue(node, IssueType.Warning, IssueId.StringPrefixIgnore);
                    }
                    _output.Write(str);
                    break;

                case SyntaxKind.CharacterLiteralExpression:
                    _output.Write(node.Token.ValueText.Bracketing(StringPair.SingleQuote));
                    break;

                default:
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.UnknownExpressionType, node.Kind);
                    break;
            }

            return node;
        }

        public override SyntaxNode VisitElementAccessExpression(ElementAccessExpressionSyntax node)
        {
            this.VisitExpression(node.Expression);
            _output.Write("[");
            if (node.ArgumentList.Arguments.Count > 1)
            {
                this.AppendCompileIssue(node, IssueType.Error, IssueId.MultiDimensionArrayAccessNotSupport);
            }
            this.MakeArgumentsList(node.ArgumentList.Arguments);
            _output.Write("]");
            return node;
        }

        public override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node)
        {
            if (IsAssignExpression(node) && IsPropertyAccess(node.Left))
            {
                if (IsPartOfAssignment(node))
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.PropertyGetAndSet);
                    return node;
                }

                if (node.Kind == SyntaxKind.AssignExpression)
                {
                    _isSetter = true;
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write("(");
                    VisitExpression(node.Right);
                    _output.Write(")");
                }
                else
                {
                    _isSetter = true;
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write("(");
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write(" {0} ", this.DecomposeOperator(node));
                    VisitExpression(node.Right);
                    _output.Write(")");
                }
            }
            else
            {
                switch (node.OperatorToken.Kind)
                {
                    case SyntaxKind.AsKeyword:
                        this.VisitExpression(node.Left);
                        //Note:as operator is used for case, jscript don't care it, ignore.
                        break;

                    case SyntaxKind.QuestionQuestionToken:
                        Visit(node.Left);
                        _output.Write(" || ");
                        Visit(node.Right);
                        break;

                    case SyntaxKind.IsKeyword:
                        Visit(node.Left);
                        _output.Write(" instanceof ");
                        Visit(node.Right);
                        break;

                    default:
                        this.VisitExpression(node.Left);
                        _output.Write(' ' + node.OperatorToken.ValueText + ' ');
                        this.VisitExpression(node.Right);
                        break;
                }
            }

            return node;
        }

        public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
        {
            var info = _semanticModel.GetSymbolInfo(node);
            _output.Write(info.Symbol.GetTypeSymbolName());
            return node;
        }

        public override SyntaxNode VisitParenthesizedExpression(ParenthesizedExpressionSyntax node)
        {
            _output.Write("(");
            this.VisitExpression(node.Expression);
            _output.Write(")");
            return node;
        }

        public override SyntaxNode VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
        {
            if (node.Kind != SyntaxKind.NegateExpression && node.Kind != SyntaxKind.LogicalNotExpression && this.IsPropertyAccess(node.Operand))
            {
                if (IsPartOfAssignment(node))
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.SelfAssignWithProperty);
                }

                _isSetter = true;
                this.Visit(node.Operand);
                _output.Write("(");
                this.Visit(node.Operand);
                _output.Write(this.TranslateUnaryOperator(node.OperatorToken));
                _output.Write(")");
            }
            else
            {
                _output.Write(node.OperatorToken.ValueText);
                this.Visit(node.Operand);
            }
            return node;
        }

        public override SyntaxNode VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node)
        {
            if (this.IsPropertyAccess(node.Operand))
            {
                if (IsPartOfAssignment(node))
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.PropertyGetAndSet);
                }

                _isSetter = true;
                this.Visit(node.Operand);
                _output.Write("(");
                this.Visit(node.Operand);
                _output.Write(this.TranslateUnaryOperator(node.OperatorToken));
                _output.Write(")");
            }
            else
            {
                this.Visit(node.Operand);
                _output.Write(node.OperatorToken.ValueText);
            }
            return node;
        }

        public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
        {
            _output.Write("this");
            return node;
        }

        public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
        {
            if (node.Expression.Kind == SyntaxKind.ThisExpression)
            {
                var nInfo = _semanticModel.GetSymbolInfo(node.Name);

                if (nInfo.Symbol != null && nInfo.Symbol.IsGlobalVariable())
                {
                    this.AppendCompileIssue(node, IssueType.Warning, IssueId.GlobalVarWithThis);
                }
            }

            if (IsPartOfDynamic(node))
            {
                _output.Write(node.GetText().ToString());//Note: Dyanmic, output without change
                this.AppendCompileIssue(node, IssueType.Warning, IssueId.DynamicType);
                return node;
            }
            else
            {
                var info = _semanticModel.GetSymbolInfo(node.Expression);

                if (info.Symbol != null && info.Symbol.Kind == SymbolKind.NamedType)
                {
                    _output.Write(info.Symbol.GetTypeSymbolName());
                    if (!IsGlobalHolder(node.Expression))
                    {
                        _output.Write(".");
                    }
                    this.Visit(node.Name);
                }
                else
                {
                    this.VisitExpression(node.Expression);
                    _output.Write(".");

                    _rightMost = node.IsRightMostMemberAccess();
                    this.Visit(node.Name);
                    _rightMost = false;
                }

                return node;
            }
        }

        public override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
        {
            if (node.Initializer != null)
            {
                if (node.ArgumentList != null && node.ArgumentList.Arguments.Count > 0)
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.CtorParamWithInitializer);
                }

                _output.Write("{");

                //Extend: Compiler API seems have bug, it can not recognize the parameter name correctly. use below walk around current
                //can not get semantic info of node.Left, shoule be fix in future, because we lost some metadata info(Attributes)
                var count = 0;
                foreach (BinaryExpressionSyntax exp in node.Initializer.Expressions)
                {
                    var left = exp.Left as IdentifierNameSyntax;

                    string name = this.BindSemanticModel(node, left.Identifier);
                    if (_jsonNameFlag.RequireQuote())
                    {
                        _output.Write("\"{0}\": ", name);
                        //_output.Write("\"{0}\": ", NamingConvention.LowerCase1stChar(left.Identifier.ValueText));
                    }
                    else
                    {
                        _output.Write("{0}: ", name);
                        //_output.Write("{0}: ", NamingConvention.LowerCase1stChar(left.Identifier.ValueText));
                    }
                    this.VisitExpression(exp.Right);

                    count++;
                    if (count < node.Initializer.Expressions.Count)
                    {
                        _output.Write(", ");
                    }
                }

                _output.Write("}");
            }
            else
            {
                var info = _semanticModel.GetTypeInfo(node);

                if (info.Type.SpecialType == SpecialType.System_Object)
                {
                    _output.Write("{}");
                }
                else if (info.Type.TypeKind == TypeKind.Delegate)
                {
                    this.MakeArgumentsList(node.ArgumentList.Arguments);
                }
                else
                {
                    var name = info.Type.GetTypeSymbolName();
                    _output.Write("new " + name + "(");

                    if (node.ArgumentList != null)
                        this.MakeArgumentsList(node.ArgumentList.Arguments);

                    _output.Write(")");
                }
            }

            return node;
        }

        public override SyntaxNode VisitBaseExpression(BaseExpressionSyntax node)
        {
            this.AppendCompileIssue(node, IssueType.Error, IssueId.BaseCallNotSupport);
            return node;
        }

        public override SyntaxNode VisitCastExpression(CastExpressionSyntax node)
        {
            //Note:Js don't care type, throw the type info away.
            VisitExpression(node.Expression);
            return node;
        }

        public override SyntaxNode VisitArrayRankSpecifier(ArrayRankSpecifierSyntax node)
        {
            if (node.Rank > 1)
            {
                this.AppendCompileIssue(node, IssueType.Error, IssueId.MultiDimensionArrayAccessNotSupport);
            }
            return node;
        }

        public override SyntaxNode VisitArrayCreationExpression(ArrayCreationExpressionSyntax node)
        {
            _output.Write("[");

            foreach (var rs in node.Type.RankSpecifiers)
                this.Visit(rs);

            int count = 0;
            if (node.Initializer != null)
            {
                foreach (var expr in node.Initializer.Expressions)
                {
                    this.VisitExpression(expr);
                    count++;
                    if (count != node.Initializer.Expressions.Count)
                        _output.Write(", ");
                }
            }

            _output.Write("]");

            return node;
        }

        public override SyntaxNode VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node)
        {
            _output.Write("function ({0}) ", this.MakeParametersList(node.ParameterList));
            _output.WriteLine("{");

            _identLevel++;
            this.Visit(node.Block);
            _identLevel--;

            _output.WriteWithIdent(this.CurrLineIdent, "}");
            return node;
        }

        public override SyntaxNode VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node)
        {
            _output.Write("{");
            int count = 0;
            foreach (var member in node.Initializers)
            {
                string left = member.NameEquals.Name.Identifier.ValueText;
                if (_jsonNameFlag.RequireQuote())
                {
                    _output.Write("\"" + NamingConvention.LowerCase1stChar(left) + "\"");
                }
                else
                {
                    _output.Write(NamingConvention.LowerCase1stChar(left));
                }
                _output.Write(": ");

                this.VisitExpression(member.Expression);
                count++;
                if (count != node.Initializers.Count)
                    _output.Write(", ");
            }
            _output.Write("}");
            return node;
        }

        public override SyntaxNode VisitGenericName(GenericNameSyntax node)
        {
            var info = _semanticModel.GetSymbolInfo(node);
            Symbol symbol = info.Symbol;
            if (symbol == null)
            {
                if (info.CandidateSymbols.Count == 1 && info.CandidateReason == CandidateReason.OverloadResolutionFailure)
                {
                    symbol = info.CandidateSymbols[0];
                    this.AppendCompileIssue(node, IssueType.Warning, IssueId.OverloadResolveOnlyOne);
                }
                else
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.SemanticBind);
                }
            }

            switch (symbol.Kind)
            {
                case SymbolKind.Method:
                    GenerateMethodCode(node, symbol);
                    break;

                default:
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.UnknownGenericName);
                    break;
            }

            return node;
        }

        public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
        {
            var info = _semanticModel.GetSymbolInfo(node);
            var symbol = info.Symbol;

            if (symbol == null)
            {
                //Note: the C# compiler already compile this project, so if there is only 1 candidate for the overload resolve fail reason, we can think this is valid symbol
                if (info.CandidateSymbols.Count == 1 && info.CandidateReason == CandidateReason.OverloadResolutionFailure)
                {
                    this.AppendCompileIssue(node, IssueType.Warning, IssueId.OverloadResolveOnlyOne);
                    symbol = info.CandidateSymbols[0];
                }
                else if (info.CandidateSymbols.Count == 0)
                {
                    this.AppendCompileIssue(node, IssueType.Warning, IssueId.FailToBindMemberAccess);
                    
                    _output.Write(NamingConvention.LowerCase1stChar(node.Identifier.ValueText));
                    return node;
                }
                else
                {
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.SemanticBind);
                    return node;
                }
            }

            if (!symbol.IsScriptSymbol())
            {
                this.AppendCompileIssue(node, IssueType.Error, IssueId.UseNonScript, symbol.Name);
            }

            switch (symbol.Kind)
            {
                case SymbolKind.Field:
                    if (node.Parent.Kind != SyntaxKind.MemberAccessExpression)
                    {
                        if (symbol.IsStatic)
                        {
                            _output.Write("{0}.", symbol.ContainingType.GetTypeSymbolName());
                        }
                        else
                        {
                            if (!symbol.IsGlobalVariable())
                                _output.Write("this.");
                        }
                    }

                    if (node.Parent.Kind == SyntaxKind.MemberAccessExpression)
                    {
                        MemberAccessExpressionSyntax pmaes = node.Parent as MemberAccessExpressionSyntax;
                        if (pmaes.Expression == node)
                        {
                            if (symbol.IsStatic)
                            {
                                _output.Write("{0}.", symbol.ContainingType.GetTypeSymbolName());
                            }
                            else
                            {
                                _output.Write("this.");
                            }
                        }
                        else
                        {
                        }
                    }

                    _output.Write(symbol.GetMemberSymbolName());
                    break;

                case SymbolKind.Local:
                case SymbolKind.Parameter:
                    _output.Write(symbol.Name);
                    break;

                case SymbolKind.NamedType:
                    var typeSymbol = symbol as TypeSymbol;
                    var name = typeSymbol.GetTypeSymbolName();
                    _output.Write(name);
                    break;

                case SymbolKind.Method:
                    GenerateMethodCode(node, symbol);
                    break;

                case SymbolKind.Property:
                    if (symbol.ContainingType.IsAnonymousType)
                    {
                        _output.Write(symbol.Name);
                    }
                    else
                    {
                        if (_isSetter && _rightMost)
                        {
                            _output.Write("set{0}", symbol.Name);
                            _isSetter = false;//Note:Reset
                        }
                        else
                        {
                            _output.Write("get{0}()", symbol.Name);
                        }
                    }
                    break;

                default:
                    this.AppendCompileIssue(node, IssueType.Error, IssueId.UnknownSymbol, symbol.Kind);
                    break;
            }
            return node;
        }

        public override SyntaxNode VisitConditionalExpression(ConditionalExpressionSyntax node)
        {
            this.VisitExpression(node.Condition);
            _output.Write(" ? ");
            this.VisitExpression(node.WhenTrue);
            _output.Write(" : ");
            this.VisitExpression(node.WhenFalse);
            return node;
        }

        public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
        {
            var info = _semanticModel.GetTypeInfo(node);

            _output.Write("function ({0}) ", node.Parameter.Identifier.ValueText);
            _output.WriteLine("{");
            _identLevel++;



            //Note:Simple lambda always have 1 line code, so add prefix ident and postfix ';'
            if (node.Body.Kind != SyntaxKind.Block)
                _output.WriteWithIdent(CurrLineIdent, string.Empty);
            if (info.ReturnValue())
                _output.Write("return ");
            this.Visit(node.Body);
            if (node.Body.Kind != SyntaxKind.Block)
                _output.WriteLine(";");

            _identLevel--;
            _output.WriteWithIdent(this.CurrLineIdent, "}");

            return node;
        }

        public override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
        {
            var info = _semanticModel.GetTypeInfo(node);

            _output.Write("function ({0}) ", this.MakeParametersList(node.ParameterList));
            _output.WriteLine("{");
            _identLevel++;
            if (node.Body.Kind != SyntaxKind.Block)
            {
                _output.WriteWithIdent(CurrLineIdent, string.Empty);
                if (info.ReturnValue())
                    _output.Write("return ");
            }
            this.Visit(node.Body);
            if (node.Body.Kind != SyntaxKind.Block)
            {
                _output.WriteLine(";");
            }
            _identLevel--;
            _output.WriteWithIdent(this.CurrLineIdent, "}");
            return node;
        }

        public override SyntaxNode VisitDefaultExpression(DefaultExpressionSyntax node)
        {
            //Note: because we can not predict what T is, unless there is constraint on T said it's class or struct.
            this.AppendCompileIssue(node, IssueType.Warning, IssueId.DefaultKeyword);
            _output.Write("null");

            return node;
        }

        public override SyntaxNode VisitTypeOfExpression(TypeOfExpressionSyntax node)
        {
            this.AppendCompileIssue(node, IssueType.Error, IssueId.TypeOfNotSupport);
            return node;
        }

        public override SyntaxNode VisitSizeOfExpression(SizeOfExpressionSyntax node)
        {
            this.AppendCompileIssue(node, IssueType.Error, IssueId.SizeOfNotSupport);
            return node;
        }
    }
}
